Skip to content

MCP Servers

MCP (Model Context Protocol) allows you to connect external tool servers to your agent. MCP servers run as separate processes and expose tools over a standardized protocol.

What is MCP?

MCP is a protocol for connecting AI agents to external tools. Instead of writing C# code for each integration, you can:

  • Use existing MCP servers (filesystem, GitHub, databases, etc.)
  • Connect to any MCP-compatible server
  • Get automatic tool discovery
Agent  ←──MCP Protocol──→  MCP Server (filesystem)
       ←──MCP Protocol──→  MCP Server (github)
       ←──MCP Protocol──→  MCP Server (database)

Quick Start

1. Create a Manifest File

Create MCP.json:

json
{
  "servers": [
    {
      "name": "filesystem",
      "command": "npx",
      "arguments": ["-y", "@anthropic/mcp-filesystem", "/workspace"],
      "description": "File operations within workspace",
      "enabled": true
    }
  ]
}

2. Register with AgentBuilder

csharp
var agent = await new AgentBuilder()
    .WithMCP("./MCP.json")
    .BuildAsync();

That's it! The agent now has access to all tools from the filesystem MCP server.


Manifest Configuration

MCPServerConfig Properties

PropertyTypeDefaultDescription
namestringrequiredUnique identifier for the server
commandstringrequiredCommand to start the server
argumentsstring[][]Command arguments
descriptionstringnullDescription shown when collapsed
enabledbooltrueWhether to load this server
enablecollapsingboolnullGroup tools under a container
requiresPermissionbooltrueRequire user approval for tools
functionResultstringnullOne-time message on expansion (appended to auto-generated)
systemPromptstringnullPersistent instructions (injected into system prompt)
timeoutint30000Connection timeout in ms
retryAttemptsint3Number of retry attempts
environmentobjectnullEnvironment variables

Full Example

json
{
  "servers": [
    {
      "name": "filesystem",
      "command": "npx",
      "arguments": ["-y", "@anthropic/mcp-filesystem", "/workspace"],
      "description": "File operations within workspace",
      "enabled": true,
      "enablecollapsing": true,
      "requiresPermission": true,
      "functionResult": "Working directory: /workspace",
      "systemPrompt": "Never write outside /workspace. Always use absolute paths.",
      "timeout": 30000,
      "retryAttempts": 3,
      "environment": {
        "NODE_ENV": "production"
      }
    },
    {
      "name": "github",
      "command": "npx",
      "arguments": ["-y", "@anthropic/mcp-github"],
      "description": "GitHub repository operations",
      "enabled": true,
      "enablecollapsing": true,
      "systemPrompt": "Use search before listing all PRs. Always include PR descriptions.",
      "environment": {
        "GITHUB_TOKEN": "${GITHUB_TOKEN}"
      }
    }
  ]
}

Registration Methods

From Manifest File

csharp
var agent = await new AgentBuilder()
    .WithMCP("./MCP.json")
    .BuildAsync();

With Options

csharp
var agent = await new AgentBuilder()
    .WithMCP("./MCP.json", options =>
    {
        options.FailOnServerError = false;      // Continue if a server fails
        options.ConnectionTimeout = TimeSpan.FromSeconds(30);
        options.MaxConcurrentServers = 10;
    })
    .BuildAsync();

From JSON String

csharp
var manifest = @"{
  ""servers"": [
    {
      ""name"": ""filesystem"",
      ""command"": ""npx"",
      ""arguments"": [""-y"", ""@anthropic/mcp-filesystem""]
    }
  ]
}";

var agent = await new AgentBuilder()
    .WithMCPContent(manifest)
    .BuildAsync();

Collapsing

When enablecollapsing is true, all tools from a server are grouped under a container:

Before expansion:           After expansion:
┌─────────────────────┐     ┌─────────────────────┐
│ MCP_filesystem      │ ──► │ read_file           │
│ (5 functions)       │     │ write_file          │
└─────────────────────┘     │ list_directory      │
                            │ create_directory    │
                            │ delete_file         │
                            └─────────────────────┘

Container Naming

Containers are named MCP_{serverName}:

  • filesystemMCP_filesystem
  • githubMCP_github

Enable Collapsing

In manifest:

json
{
  "name": "filesystem",
  "enablecollapsing": true,
  "description": "File operations"
}

The description appears in the container's tool definition.


Server Instructions

MCP servers support the same dual-context architecture as C# tools and Client tools:

ParameterLocationLifetimeUse For
functionResultConversation historyOne-time on expansionAdditional context (appended to auto-generated message)
systemPromptSystem promptEvery turn while expandedCritical rules, workflow

** Requires Collapsing:** functionResult and systemPrompt only work when enablecollapsing: true. If collapsing is disabled, instructions are ignored and validation will fail.

Important: The system automatically generates a base expansion message:

"{serverName} server expanded. Available functions: {FunctionList}"

Your functionResult is appended to this auto-generated message. Don't duplicate the expansion info—use it only for additional context. Pass null if the auto-generated message is sufficient.

json
{
  "name": "filesystem",
  "command": "npx",
  "arguments": ["-y", "@anthropic/mcp-filesystem", "/workspace"],
  "description": "File operations within workspace",
  "enablecollapsing": true,
  "functionResult": "Working directory: /workspace",
  "systemPrompt": "Never write outside /workspace. Always use absolute paths."
}

Via AgentConfig (Legacy)

You can also provide instructions via MCPServerInstructions in AgentConfig. These are injected as SystemPrompt (persistent):

csharp
var config = new AgentConfig
{
    Collapsing = new CollapsingConfig
    {
        MCPServerInstructions = new Dictionary<string, string>
        {
            ["filesystem"] = @"
                FILESYSTEM RULES:
                - Always use absolute paths
                - Check if file exists before reading
                - Never write outside /workspace"
        }
    }
};

Note: Manifest-level systemPrompt and MCPServerInstructions both provide persistent instructions. Use the manifest approach for new projects.


Permissions

By default, MCP tools require user permission (requiresPermission: true). This is a safety feature since MCP servers can perform arbitrary operations.

Disable for Trusted Servers

json
{
  "name": "readonly-docs",
  "command": "npx",
  "arguments": ["-y", "@example/docs-server"],
  "requiresPermission": false
}

Only disable for read-only or trusted servers.


Environment Variables

Pass environment variables to MCP servers:

json
{
  "name": "github",
  "command": "npx",
  "arguments": ["-y", "@anthropic/mcp-github"],
  "environment": {
    "GITHUB_TOKEN": "${GITHUB_TOKEN}",
    "GITHUB_ORG": "my-org"
  }
}

Use ${VAR_NAME} syntax to reference system environment variables.


Common MCP Servers

ServerPackageDescription
Filesystem@anthropic/mcp-filesystemRead/write files
GitHub@anthropic/mcp-githubRepository operations
PostgreSQL@anthropic/mcp-postgresDatabase queries
Brave Search@anthropic/mcp-brave-searchWeb search
Memory@anthropic/mcp-memoryPersistent key-value store
Slack@anthropic/mcp-slackMessaging and channels
Google Drive@anthropic/mcp-gdriveDocument access
Puppeteer@anthropic/mcp-puppeteerBrowser automation

Version Pinning

By default npx -y installs the latest version of a package on every startup. In production, pin to a specific version to prevent unexpected breaking changes:

json
{
  "name": "filesystem",
  "command": "npx",
  "arguments": ["-y", "@anthropic/mcp-filesystem@1.2.3", "/workspace"]
}

Or pre-install and reference directly:

json
{
  "name": "filesystem",
  "command": "node",
  "arguments": ["./node_modules/@anthropic/mcp-filesystem/dist/index.js", "/workspace"]
}

Recommendation: Pin versions in production (@1.2.3), use latest (-y without version) in development.

Example: Multiple Servers

json
{
  "servers": [
    {
      "name": "filesystem",
      "command": "npx",
      "arguments": ["-y", "@anthropic/mcp-filesystem@1.2.3", "/workspace"],
      "enablecollapsing": true,
      "systemPrompt": "Always use absolute paths. Never write outside /workspace."
    },
    {
      "name": "github",
      "command": "npx",
      "arguments": ["-y", "@anthropic/mcp-github@0.6.2"],
      "enablecollapsing": true,
      "systemPrompt": "Use search before listing all PRs. Always include PR descriptions.",
      "environment": {
        "GITHUB_TOKEN": "${GITHUB_TOKEN}"
      }
    },
    {
      "name": "search",
      "command": "npx",
      "arguments": ["-y", "@anthropic/mcp-brave-search@0.3.1"],
      "enablecollapsing": false,
      "environment": {
        "BRAVE_API_KEY": "${BRAVE_API_KEY}"
      }
    }
  ]
}

---

## Authentication Patterns

MCP servers authenticate via environment variables passed in the manifest. Never hardcode credentials.

### API Key (most common)

```json
{
  "name": "github",
  "command": "npx",
  "arguments": ["-y", "@anthropic/mcp-github"],
  "environment": {
    "GITHUB_TOKEN": "${GITHUB_TOKEN}"
  }
}

${VAR_NAME} is resolved from the host process's environment at startup.

OAuth Token

Some servers accept OAuth tokens the same way — just pass the token as an environment variable:

json
{
  "name": "gdrive",
  "command": "npx",
  "arguments": ["-y", "@anthropic/mcp-gdrive"],
  "environment": {
    "GDRIVE_OAUTH_TOKEN": "${GDRIVE_OAUTH_TOKEN}"
  }
}

Multiple Credentials

json
{
  "name": "myserver",
  "command": "node",
  "arguments": ["./myserver/index.js"],
  "environment": {
    "API_KEY": "${MY_API_KEY}",
    "API_SECRET": "${MY_API_SECRET}",
    "BASE_URL": "https://api.example.com"
  }
}

Rotating Credentials

For credentials that expire (e.g. short-lived tokens), register the MCP server via C# attribute instead of the manifest, so you can resolve the token at build time. ISecretResolver is injected automatically by the source generator — just declare it as a constructor parameter:

csharp
public partial class MyTools(ISecretResolver secrets)
{
    [MCPServer]
    public MCPServerConfig MyApi() => new()
    {
        Name = "myapi",
        Command = "node",
        Arguments = ["./myserver/index.js"],
        Environment = new()
        {
            ["API_TOKEN"] = secrets.Require("myapi:token")
        }
    };
}

No DI container registration needed — the builder passes the configured resolver at startup.


Error Handling

Server Startup Failures

By default, if one server fails to start, the agent continues with others:

csharp
.WithMCP("./MCP.json", options =>
{
    options.FailOnServerError = false;  // Default
})

Set FailOnServerError = true to fail fast if any server fails.

Timeouts

Configure connection timeout:

csharp
.WithMCP("./MCP.json", options =>
{
    options.ConnectionTimeout = TimeSpan.FromSeconds(60);
})

Or per-server in manifest:

json
{
  "name": "slow-server",
  "timeout": 60000
}

Troubleshooting

IssueCauseFix
Server not foundcommand not in PATHUse full path or ensure npx is available
Tools not appearingenabled: falseSet enabled: true
Permission deniedServer needs API keyCheck environment variables
TimeoutServer slow to startIncrease timeout

Debug: Check Loaded Tools

csharp
var agent = await new AgentBuilder()
    .WithMCP("./MCP.json")
    .BuildAsync();

// List all tools
foreach (var tool in agent.Tools)
{
    Console.WriteLine($"{tool.Name}: {tool.Description}");
}

C# Attribute Registration

In addition to JSON manifests, MCP servers can be registered directly in C# using the [MCPServer] attribute on a toolkit method that returns MCPServerConfig. This is useful when you need constructor injection (e.g. ISecretResolver) or want to keep the server definition co-located with related tools.

The class must be partial — the source generator produces the registration glue.

Basic Usage

csharp
public partial class MyTools
{
    [MCPServer]
    public MCPServerConfig FileSystem() => new()
    {
        Name = "filesystem",
        Command = "npx",
        Arguments = ["-y", "@anthropic/mcp-filesystem", "/workspace"],
        Description = "File operations within workspace"
    };
}

From Manifest

Load config from MCP.json by server name. The method returns null — the runtime reads the manifest entry instead:

csharp
public partial class MyTools
{
    [MCPServer("filesystem", FromManifest = "./MCP.json")]
    public MCPServerConfig? FileSystem() => null;
}

Nested Collapsing

When CollapseWithinToolkit = true, the MCP server's tools appear behind their own container nested inside the parent toolkit — two expansions required (toolkit first, then MCP server):

csharp
[Collapse("Research tools")]
public partial class ResearchTools
{
    [MCPServer(CollapseWithinToolkit = true)]
    public MCPServerConfig BraveSearch() => new()
    {
        Name = "brave",
        Command = "npx",
        Arguments = ["-y", "@anthropic/mcp-brave-search"],
        SystemPrompt = "Always cite sources. Prefer authoritative references."
    };

    [AIFunction]
    public string SummarizeResults(string content) { /* ... */ }
}

With Permissions

csharp
public partial class MyTools
{
    [MCPServer]
    [RequiresPermission]
    public MCPServerConfig GitServer() => new()
    {
        Name = "git",
        Command = "git-mcp",
        SystemPrompt = "Only operate on the current repository. Never force-push."
    };
}

Conditional MCP Servers

Use the generic form [MCPServer<TMetadata>] to show or hide the server based on runtime metadata:

csharp
public class SearchMetadata : IToolMetadata
{
    public bool HasBraveKey { get; set; }
}

public partial class SearchTools(string apiKey)
{
    [MCPServer<SearchMetadata>]
    [ConditionalFunction("HasBraveKey")]
    public MCPServerConfig BraveSearch() => new()
    {
        Name = "brave",
        Command = "npx",
        Arguments = ["-y", "@anthropic/mcp-brave-search"],
        Environment = new() { ["BRAVE_API_KEY"] = apiKey }
    };
}

Register with metadata:

csharp
var agent = await new AgentBuilder()
    .WithToolkit<SearchTools>(new SearchMetadata { HasBraveKey = true })
    .BuildAsync();

Attribute Reference

PropertyTypeDescription
ServerNamestring?Server name to look up when using FromManifest mode. Also settable via constructor: [MCPServer("name")].
Namestring?Display name override (defaults to method name).
Descriptionstring?Description override for the collapsed container. Auto-fetched from server's ServerInfo if not set.
FromManifeststring?Path to MCP.json to load server config from. Method should return null.
CollapseWithinToolkitboolGroup MCP tools behind their own nested MCP_* container (default: false).

Best Practices

  1. Use collapsing for servers with many tools to reduce context clutter.

  2. Set requiresPermission: true for servers that can modify data.

  3. Provide descriptions to help the agent understand what each server does.

  4. Use environment variables for secrets—never hardcode tokens.

  5. Set appropriate timeouts based on server startup time.

  6. Use dual-context instructions: Put critical rules in systemPrompt, additional context in functionResult.

  7. Don't duplicate auto-generated messages in functionResult—they waste tokens.

json
{
  "name": "database",
  "command": "npx",
  "arguments": ["-y", "@example/mcp-postgres"],
  "description": "Query the production database (read-only)",
  "enablecollapsing": true,
  "requiresPermission": true,
  "functionResult": "Connected to: production (read-only)",
  "systemPrompt": "LIMIT all queries to 1000 rows. Never use DELETE or UPDATE.",
  "timeout": 10000,
  "environment": {
    "DATABASE_URL": "${DATABASE_URL}"
  }
}

Released under the MIT License.